#pragma once

typedef void (*deleter)(void *obj);

template <class T>
class shared_pointer
{
public:
	shared_pointer();
	explicit shared_pointer(T *p, deleter d = NULL);
	shared_pointer(const shared_pointer<T> &p);
	~shared_pointer();

	shared_pointer &operator = (const shared_pointer<T> &p);

	T &operator *() const;
	T *operator ->() const;
	T *ptr() const;

	bool operator == (const shared_pointer<T> &p);
	bool operator < (const shared_pointer<T> &p);

	bool is_null() const;

	void release();

	shared_pointer<T> clone() const;

private:
	int		*m_refcount;

	T		*m_pointer;

	deleter	m_deleter;
};

template <class T>
inline shared_pointer<T>::shared_pointer() : m_refcount(0), m_pointer(0), m_deleter(0) {}

template <class T>
inline shared_pointer<T>::shared_pointer(T *p, deleter d = NULL) : m_refcount(p ? new int : 0), m_pointer(p), m_deleter(d)
{
	if(m_refcount != 0)
		*m_refcount = 0;
}

template <class T>
inline shared_pointer<T>::shared_pointer(const shared_pointer<T> &p)
{
	m_pointer = p.m_pointer;
	m_deleter = p.m_deleter;
	m_refcount = p.m_refcount;
	if(m_refcount != NULL)
		(*m_refcount)++;
}

template <class T>
inline shared_pointer<T>::~shared_pointer() { release(); }

template <class T>
inline shared_pointer<T> &shared_pointer<T>::operator = (const shared_pointer<T> &p)
{
	if(p.m_pointer != m_pointer) {
		release();
		m_pointer = p.m_pointer;
		m_deleter = p.m_deleter;
		m_refcount = p.m_refcount;
		if(m_refcount != NULL)
			(*m_refcount)++;
	}
	return *this;
}

template <class T>
inline T &shared_pointer<T>::operator *() const { return *m_pointer; }

template <class T>
inline T *shared_pointer<T>::operator ->() const { return m_pointer; }

template <class T>
T *shared_pointer<T>::ptr() const { return m_pointer; }

template <class T>
inline void shared_pointer<T>::release()
{
	if(m_refcount != 0) {
		if((*m_refcount)-- == 0) {
			if(m_deleter != 0)
				m_deleter(m_pointer);
			else
				delete m_pointer;

			delete m_refcount;

			m_refcount = 0;
			m_deleter = 0;
			m_pointer = 0;
		}
	}
}

template <class T>
inline bool shared_pointer<T>::is_null() const { return m_pointer ? false : true; }

template <class T>
inline bool shared_pointer<T>::operator == (const shared_pointer<T> &p) { return m_pointer == p.m_pointer; }

template <class T>
inline bool shared_pointer<T>::operator < (const shared_pointer<T> &p) { return m_pointer < p.m_pointer; }

template <class T>
shared_pointer<T> shared_pointer<T>::clone() const { return is_null() ? shared_pointer<T>() : shared_pointer<T>(new T(*ptr())); }